/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Windows.Threading;
using Xceed.Silverlight.Data.Stats;
using Xceed.Silverlight.Compatibility;

namespace Xceed.Silverlight.Data
{
  public abstract class DataSourceProvider
  {
    #region Static Fields

    private static readonly Guid ExecuteCountQueryCallId = DataUtils.CreateCallId();
    private static readonly Guid ExecuteDataQueryCallId = DataUtils.CreateCallId();
    private static readonly Guid ExecuteGroupCountQueryCallId = DataUtils.CreateCallId();
    private static readonly Guid ExecuteStatQueryCallId = DataUtils.CreateCallId();
    private static readonly Guid ExecuteStatQueryPrerequisiteFunctionsCallId = DataUtils.CreateCallId();
    private static readonly Guid ExecuteNativeStatQueryCallId = DataUtils.CreateCallId();
    private static readonly Guid ExecuteExceptionCallId = DataUtils.CreateCallId();

    #endregion

    #region Constructor

    internal DataSourceProvider()
    {
    }

    #endregion

    #region PageSize Property

    public int PageSize
    {
      get
      {
        return m_pageSize;
      }

      set
      {
        if( value < 0 )
          throw new ArgumentOutOfRangeException( "PageSize", "PageSize must be greater than or equal to zero." );

        m_pageSize = value;
      }
    }

    private int m_pageSize; //0

    #endregion

    #region CanFilter Property

    public virtual bool CanFilter
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanJumpToItem Property

    public virtual bool CanJumpToItem
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanJump Property

    public virtual bool CanJump
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanFilterGroup Property

    public virtual bool CanFilterGroup
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanSkip Property

    public virtual bool CanSkip
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanCount Property

    public virtual bool CanCount
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanGroupCount Property

    public virtual bool CanGroupCount
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanCalculateStats Property

    public virtual bool CanCalculateStats
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region ElementType Property

    protected internal abstract Type ElementType
    {
      get;
    }

    #endregion

    #region CanSort Internal Property

    internal virtual bool CanSort
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region IsAsynchronous Internal Property

    internal abstract bool IsAsynchronous
    {
      get;
    }

    #endregion

    #region IsCachingAllowed Internal Property

    internal abstract bool IsCachingAllowed
    {
      get;
    }

    #endregion

    #region IsBufferingAllowed Internal Property

    internal abstract bool IsBufferingAllowed
    {
      get;
    }

    #endregion

    #region IsQueryLengthLimited Internal Property

    internal virtual bool IsQueryLengthLimited
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region IsPrimaryKeyVolatile Internal Property

    internal virtual bool IsPrimaryKeyVolatile
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanFilterPredicate Internal Property

    internal virtual bool CanFilterPredicate
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanJumpToDeletedItem Internal Property

    internal virtual bool CanJumpToDeletedItem
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region PrimaryKeyInfo Internal Property

    internal IEnumerable<SortDescription> PrimaryKeyInfo
    {
      get
      {
        if( m_primaryKeyInfo == null )
        {
          m_primaryKeyInfo = new List<SortDescription>( 3 );

          IEnumerable<SortDescription> primaryKeyList = this.GetPrimaryKeyInfo();

          if( primaryKeyList != null )
          {
            m_primaryKeyInfo.AddRange( primaryKeyList );
          }
        }

        return m_primaryKeyInfo;
      }
    }

    private List<SortDescription> m_primaryKeyInfo; //null

    #endregion

    #region UnhandledException Event

    public event EventHandler<UnhandledDataExceptionEventArgs> UnhandledException;

    internal void HandleAsyncException( Exception e )
    {
      Debug.Assert( false, "An exception has been thrown" );

      Action a = () =>
      {
        throw e;
      };

      UIDispatcher.Instance.Invoke( a );
    }

    private void OnUnhandledException( UnhandledDataExceptionEventArgs e )
    {
      Action a = () => this.RaiseUnhandledException( e );

      UIDispatcher.Instance.Invoke( a );
    }

    private void RaiseUnhandledException( UnhandledDataExceptionEventArgs e )
    {
      if( this.UnhandledException != null )
      {
        this.UnhandledException( this, e );
      }
    }

    private ErrorResultAction HandleException( Exception exception, int retryCount )
    {
      UnhandledDataExceptionEventArgs e = new UnhandledDataExceptionEventArgs(
                                            exception,
                                            ErrorResultAction.Abort,
                                            retryCount );

      this.OnUnhandledException( e );

      return e.Result;
    }

    #endregion

    #region DataSourceChanged Internal Event

    internal event EventHandler<DataSourceChangedEventArgs> DataSourceChanged;

    protected void OnDataSourceChanged( DataSourceChangedEventArgs e )
    {
      Action a = () => this.RaiseDataSourceChanged( e );

      UIDispatcher.Instance.Invoke( a );
    }

    private void RaiseDataSourceChanged( DataSourceChangedEventArgs e )
    {
      if( this.DataSourceChanged != null )
      {
        DataSourceChangedEventArgs newEventArgs = this.WrapDataObjectEventArgs( e );
        bool isRelevant = ( newEventArgs.Action == DataSourceChangedAction.Reset )
                       || ( newEventArgs.Action == DataSourceChangedAction.Add && newEventArgs.NewItems.Count > 0 )
                       || ( newEventArgs.Action == DataSourceChangedAction.Remove && newEventArgs.OldItems.Count > 0 )
                       || ( newEventArgs.Action == DataSourceChangedAction.Replace && newEventArgs.NewItems.Count > 0 )
                       || ( newEventArgs.Action == DataSourceChangedAction.ItemChanged && newEventArgs.ItemsChanged.Count > 0 );

        // Make sure the event args has some relevant information before raising the event.
        // For instance, it is useless to raise an "Add" if no item has been added.
        if( isRelevant )
        {
          IList<object> newItems = newEventArgs.NewItems;
          IList<object> oldItems = newEventArgs.OldItems;
          bool oneToOneMapping = ( newItems.Count == oldItems.Count );

          // Mark each item removed has deleted to prevent any further ItemJumpRequest on it.
          for( int i = 0; i < oldItems.Count; i++ )
          {
            bool markAsRemoved = true;

            if( oneToOneMapping )
            {
              // On a replace action, the old item may have been moved to another location.
              // We must not tag it as removed if it is still in the source.
              markAsRemoved = !newItems.Contains( oldItems[ i ] );
            }

            if( markAsRemoved )
            {
              ( ( RawDataItem )oldItems[ i ] ).Delete();
            }
          }

          this.DataSourceChanged( this, newEventArgs );
        }
      }
    }

    #endregion

    protected abstract IEnumerable<SortDescription> GetPrimaryKeyInfo();

    protected virtual IEnumerable<FilterOperator> GetSupportedFilterOperators( Type dataType )
    {
      if( dataType == null )
        yield break;

      // Nullable<> is an acceptable type if the underlying type is also acceptable.
      if( dataType.IsNullableStruct() )
      {
        dataType = Nullable.GetUnderlyingType( dataType );
      }

      // Here are the data types that supports the equality operators (==, !=).
      if( dataType.IsPrimitive
        || dataType.IsEnum
        || typeof( IEquatable<> ).MakeGenericType( dataType ).IsAssignableFrom( dataType )
        || typeof( IComparable ).IsAssignableFrom( dataType ) )
      {
        yield return FilterOperator.Equal;
        yield return FilterOperator.NotEqual;
      }

      // Here are the data types that supports the basic comparison operators (<, <=, >, >=).
      if( dataType.IsPrimitive
        || dataType.IsEnum
        || typeof( IComparable ).IsAssignableFrom( dataType ) )
      {
        yield return FilterOperator.LessThan;
        yield return FilterOperator.LessThanOrEqual;
        yield return FilterOperator.GreaterThan;
        yield return FilterOperator.GreaterThanOrEqual;
      }

      // Here are the data types that supports the searching functions (Contains, StartsWith, EndsWith, ...).
      if( typeof( string ) == dataType )
      {
        yield return FilterOperator.StartsWith;
        yield return FilterOperator.EndsWith;
        yield return FilterOperator.Contains;
        yield return FilterOperator.ContainedIn;
      }
    }

    protected virtual IEnumerable<Type> GetSupportedStatFunctions( Type dataType )
    {
      yield break;
    }

    protected virtual bool CanSortBy( IEnumerable<SortDescription> sortOrder )
    {
      if( sortOrder != null )
      {
        foreach( SortDescription sd in sortOrder )
        {
          Type propertyType;
          if( !this.FindElementMemberType( sd.PropertyName, out propertyType ) )
            return false;

          // Nullable<> is an acceptable type if the underlying type is also acceptable.
          if( propertyType.IsNullableStruct() )
          {
            propertyType = Nullable.GetUnderlyingType( propertyType );
          }

          if( !typeof( IComparable ).IsAssignableFrom( propertyType ) )
            return false;
        }
      }

      return true;
    }

    internal virtual IEnumerable<FilterOperator> SupportedFilterOperators( Type dataType )
    {
      return this.GetSupportedFilterOperators( dataType );
    }

    internal virtual IEnumerable<Type> SupportedStatFunctions( Type dataType )
    {
      return this.GetSupportedStatFunctions( dataType );
    }

    internal virtual bool IsStatFunctionSupportedNatively( StatFunction statFunction )
    {
      return this.IsStatFunctionSupported( statFunction );
    }

    internal bool IsStatFunctionSupported( StatFunction statFunction )
    {
      if( statFunction == null )
        return false;

      List<Type> dataTypes = new List<Type>();

      if( statFunction.GetSourcePropertyNames().Count > 0 )
      {
        dataTypes.AddRange( ( from spn in statFunction.GetSourcePropertyNames()
                              select this.GetElementMemberType( spn ) ).Distinct() );
      }
      else
      {
        dataTypes.Add( null );
      }

      Type statFunctionType = statFunction.GetType();
      bool isSupported = false;

      foreach( Type dataType in dataTypes )
      {
        isSupported = false;

        foreach( Type sft in this.SupportedStatFunctions( dataType ) )
        {
          isSupported = ( sft == statFunctionType )
                     || ( sft.IsAbstract && sft.IsAssignableFrom( statFunctionType ) );

          if( isSupported )
            break;
        }

        if( !isSupported )
          break;
      }

      return isSupported;
    }

    internal virtual bool IsSortOrderSupported( IEnumerable<SortDescription> sortOrder )
    {
      return this.CanSortBy( sortOrder );
    }

    internal bool CanFilterBy( Predicate<object> predicate )
    {
      return ( predicate == null )
          || ( this.CanFilterPredicate );
    }

    internal bool CanFilterBy( FilterExpressionBase filterExpression )
    {
      // There is no filter at all.
      if( filterExpression == null )
        return true;

      // The DataSourceProvider doesn't support filtering at all.
      if( !this.CanFilter )
        return false;

      CanFilterByVisitor verifier = new CanFilterByVisitor(
                                      filterExpression,
                                      this.GetElementMemberType,
                                      this.GetSupportedFilterOperators );

      return verifier.Result;
    }

    internal virtual bool CanJumpTo( IEnumerable<SortDescription> properties )
    {
      return this.CanJump;
    }

    internal virtual int GetPageSize()
    {
      return this.PageSize;
    }

    internal void CheckMinimumRequirements()
    {
      int keyCount = this.PrimaryKeyInfo.Count();

      // A primary key must exists.
      if( keyCount <= 0 )
        throw new InvalidOperationException( "An attempt was made to use a data source that has not provided a primary key." );
    }

    internal abstract IAsyncResult BeginExecuteCountQueryCore(
      CountQueryParameters queryParameters,
      AsyncCallback callback,
      object state );

    internal abstract CountQueryResult EndExecuteCountQueryCore( IAsyncResult asyncResult );

    internal abstract IAsyncResult BeginExecuteDataQueryCore(
      DataQueryParameters queryParameters,
      AsyncCallback callback,
      object state );

    internal abstract DataQueryResult EndExecuteDataQueryCore( IAsyncResult asyncResult );

    internal abstract IAsyncResult BeginExecuteGroupCountQueryCore(
      GroupCountQueryParameters queryParameters,
      AsyncCallback callback,
      object state );

    internal abstract GroupCountQueryResult EndExecuteGroupCountQueryCore( IAsyncResult asyncResult );

    internal abstract IAsyncResult BeginExecuteStatQueryCore(
      StatQueryParameters queryParameters,
      AsyncCallback callback,
      object state );

    internal abstract StatQueryResult EndExecuteStatQueryCore( IAsyncResult asyncResult );

    internal IAsyncResult BeginExecuteCountQueryInternal(
      CountQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      if( !this.CanCount )
        throw new DataInternalException( "The underlying data source does not support counting." );

      if( queryParameters.Filter != null )
      {
        if( ( queryParameters.Filter.Filter != null ) && ( !this.CanFilterPredicate ) )
          throw new DataInternalException( "The underlying data source does not support filtering." );
      }

      if( ( queryParameters.Jump != null ) && ( !this.CanJump ) )
        throw new DataInternalException( "The underlying data source does not support jumping." );

      if( ( queryParameters.FilterGroup != null ) && ( !this.CanFilterGroup ) )
        throw new DataInternalException( "The underlying data source does not support group filtering." );

      queryParameters = this.UnwrapLayer( queryParameters );

      return this.BeginExecute(
               DataSourceProvider.ExecuteCountQueryCallId,
               ( cb, st ) => this.BeginExecuteCountQueryCore( queryParameters, cb, st ),
               this.EndExecuteCountQueryCore,
               callback,
               state );
    }

    internal CountQueryResult EndExecuteCountQueryInternal( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( DataSourceProvider.ExecuteCountQueryCallId );

      Tuple<object, Exception> result = asyncResult.GetCallResult<Tuple<object, Exception>>();
      CountQueryResult queryResult = result.Item1 as CountQueryResult;
      Exception exception = result.Item2;

      if( exception != null )
        throw new DataSourceProviderException( "An unhandled exception was raised by the DataSourceProvider. Refer to the InnerException for more information.", exception );

      if( queryResult == null )
        throw new DataSourceProviderException( "The query result is required." );

      if( queryResult.Count < 0 )
        throw new DataSourceProviderException( "The count value cannot be less than zero." );

      return this.WrapDataObjectQueryResult( queryResult );
    }

    internal IAsyncResult BeginExecuteDataQueryInternal(
      DataQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      if( ( queryParameters.Skip != null ) && ( !this.CanSkip ) )
        throw new DataInternalException( "The underlying data source does not support skipping." );

      if( ( queryParameters.Sort != null ) && ( !this.CanSort ) )
        throw new DataInternalException( "The underlying data source does not support sorting." );

      if( queryParameters.Filter != null )
      {
        if( ( queryParameters.Filter.Filter != null ) && ( !this.CanFilterPredicate ) )
          throw new DataInternalException( "The underlying data source does not support filtering." );
      }

      if( ( queryParameters.ItemJump != null ) && ( !this.CanJumpToItem ) )
        throw new DataInternalException( "The underlying data source does not support jumping to items." );

      if( ( queryParameters.Jump != null ) && ( !this.CanJump ) )
        throw new DataInternalException( "The underlying data source does not support jumping." );

      queryParameters = this.UnwrapLayer( queryParameters );

      return this.BeginExecute(
               DataSourceProvider.ExecuteDataQueryCallId,
               ( cb, st ) => this.BeginExecuteDataQueryCore( queryParameters, cb, st ),
               this.EndExecuteDataQueryCore,
               callback,
               state );
    }

    internal DataQueryResult EndExecuteDataQueryInternal( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( DataSourceProvider.ExecuteDataQueryCallId );

      Tuple<object, Exception> result = asyncResult.GetCallResult<Tuple<object, Exception>>();
      DataQueryResult queryResult = result.Item1 as DataQueryResult;
      Exception exception = result.Item2;

      if( exception != null )
        throw new DataSourceProviderException( "An unhandled exception was raised by the DataSourceProvider. Refer to the InnerException for more information.", exception );

      if( queryResult == null )
        throw new DataSourceProviderException( "The query result is required." );

      if( queryResult.InlineCount.GetValueOrDefault( 0 ) < 0 )
        throw new DataSourceProviderException( "The inline count cannot be less than zero." );

      if( queryResult.TotalCount.GetValueOrDefault( 0 ) < 0 )
        throw new DataSourceProviderException( "The total count cannot be less than zero." );

      return this.WrapDataObjectQueryResult( queryResult );
    }

    internal IAsyncResult BeginExecuteGroupCountQueryInternal(
      GroupCountQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      if( !this.CanGroupCount )
        throw new DataInternalException( "The underlying data source does not support group counting." );

      if( queryParameters.Filter != null )
      {
        if( ( queryParameters.Filter.Filter != null ) && ( !this.CanFilterPredicate ) )
          throw new DataInternalException( "The underlying data source does not support filtering." );
      }

      if( ( queryParameters.Jump != null ) && ( !this.CanJump ) )
        throw new DataInternalException( "The underlying data source does not support jumping." );

      if( ( queryParameters.FilterGroup != null ) && ( !this.CanFilterGroup ) )
        throw new DataInternalException( "The underlying data source does not support group filtering." );

      queryParameters = this.UnwrapLayer( queryParameters );

      return this.BeginExecute(
               DataSourceProvider.ExecuteGroupCountQueryCallId,
               ( cb, st ) => this.BeginExecuteGroupCountQueryCore( queryParameters, cb, st ),
               this.EndExecuteGroupCountQueryCore,
               callback,
               state );
    }

    internal GroupCountQueryResult EndExecuteGroupCountQueryInternal( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( DataSourceProvider.ExecuteGroupCountQueryCallId );

      Tuple<object, Exception> result = asyncResult.GetCallResult<Tuple<object, Exception>>();
      GroupCountQueryResult queryResult = result.Item1 as GroupCountQueryResult;
      Exception exception = result.Item2;

      if( exception != null )
        throw new DataSourceProviderException( "An unhandled exception was raised by the DataSourceProvider. Refer to the InnerException for more information.", exception );

      if( queryResult == null )
        throw new DataSourceProviderException( "The query result is required." );

      if( queryResult.GroupCounts == null )
        throw new DataSourceProviderException( "The query result must provided a GroupCounts value." );

      return this.WrapDataObjectQueryResult( queryResult );
    }

    internal IAsyncResult BeginExecuteStatQueryInternal(
      StatQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      if( !this.CanCalculateStats )
        throw new DataInternalException( "The underlying data source does not support statistical functions." );

      if( queryParameters.Filter != null )
      {
        if( ( queryParameters.Filter.Filter != null ) && ( !this.CanFilterPredicate ) )
          throw new DataInternalException( "The underlying data source does not support filtering." );
      }

      if( ( queryParameters.FilterGroup != null ) && ( !this.CanFilterGroup ) )
        throw new DataInternalException( "The underlying data source does not support group filtering." );

      queryParameters = this.UnwrapLayer( queryParameters );

      StatFunction statFunction = ( queryParameters.Function != null )
                                    ? queryParameters.Function.StatFunction
                                    : null;

      if( statFunction == null )
        throw new DataInternalException();

      this.CheckIfStatFunctionIsInCalculation( statFunction );
      this.InitializeStatFunction( statFunction );

      IAsyncResult asyncResult;

      // The StatFunction is supported as it is by the DataSourceProvider.
      if( this.IsStatFunctionSupportedNatively( statFunction ) )
      {
        asyncResult = this.StartNativeStatQuery(
                        queryParameters,
                        statFunction,
                        null,
                        callback,
                        state );
      }
      // The StatFunction isn't supported as it is by the DataSourceProvider.
      // Try to calculate the StatFunction with its prerequisite StatFunctions.
      else
      {
        asyncResult = this.StartPrerequisiteStatQuery(
                        queryParameters,
                        statFunction,
                        callback,
                        state );
      }

      return asyncResult;
    }

    internal StatQueryResult EndExecuteStatQueryInternal( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( DataSourceProvider.ExecuteStatQueryCallId );

      Tuple<object, Exception> result = asyncResult.GetCallResult<Tuple<object, Exception>>();
      StatQueryResult queryResult = result.Item1 as StatQueryResult;
      Exception exception = result.Item2;

      if( exception != null )
      {
        queryResult = new StatQueryResult( new StatResult( exception ) );
      }

      if( queryResult == null )
        throw new DataSourceProviderException( "The query result is required." );

      return this.WrapDataObjectQueryResult( queryResult );
    }

    internal void CancelQuery( IAsyncResult asyncResult )
    {
      m_taskScheduler.Cancel( asyncResult );
    }

    private IAsyncResult BeginExecute(
      Guid callId,
      Func<AsyncCallback, object, IAsyncResult> beginExecuteMethod,
      Func<IAsyncResult, object> endExecuteMethod,
      AsyncCallback callback,
      object state )
    {
      AsyncResult asyncResult = new AsyncResult( callId, callback, state );

      bool isCompletedSynchronously = true;
      IAsyncResult taskId = asyncResult;
      RetryAttempt taskRetryAttempt = new RetryAttempt();

      AsyncCallback queryCallback = ( IAsyncResult ar ) =>
      {
        Action<object> handleQueryResult = null;

        // The result must not be returned synchronously if the task
        // has been delayed by the task scheduler.
        if( isCompletedSynchronously && ar.CompletedSynchronously )
        {
          handleQueryResult = asyncResult.StartSync;
        }
        else
        {
          handleQueryResult = ( object qr ) =>
          {
            try
            {
              asyncResult.Start( qr, true );
            }
            catch( Exception e )
            {
              Exception callbackException = e;

              if( !( e is DataSourceProviderException ) )
              {
                callbackException = new DataSourceProviderException( "An unhandled exception was raised by the DataSourceProvider. Refer to the InnerException for more information.", e );
              }

              this.HandleAsyncException( callbackException );
            }
          };
        }

        this.EndExecute(
          handleQueryResult,
          endExecuteMethod,
          ar,
          taskId,
          taskRetryAttempt );
      };

      Action task = () =>
      {
        IAsyncResult ar = null;

        try
        {
          ar = beginExecuteMethod.Invoke( queryCallback, null );
        }
        catch( Exception e )
        {
          ErrorResultAction errorResult = this.HandleException( e, taskRetryAttempt.Count );

          if( errorResult == ErrorResultAction.Retry )
          {
            taskRetryAttempt.Increment();
            m_taskScheduler.Restart( taskId );
          }
          else if( errorResult == ErrorResultAction.Abort )
          {
            AsyncResult exceptionAsyncResult = new AsyncResult( DataSourceProvider.ExecuteExceptionCallId, queryCallback, null );

            if( isCompletedSynchronously )
            {
              exceptionAsyncResult.StartSync( e );
            }
            else
            {
              exceptionAsyncResult.StartAsync( e );
            }       
          }
          else
          {
            // A new type of result has been created but hasn't been handled.
            if( e is DataSourceProviderException )
            {
              throw new DataSourceProviderException( "An unhandled exception was raised by the DataSourceProvider. Refer to the InnerException for more information.", e );
            }
            else
            {
              throw;
            }
          }
        }
      };

      // Add the current task to the task scheduler's queue.  
      // The task may not be called right away.
      m_taskScheduler.Enqueue( taskId, task );

      // Any task completed after this point is considered asynchronous.
      isCompletedSynchronously = false;

      return asyncResult;
    }

    private void EndExecute(
      Action<object> sendResultMethod,
      Func<IAsyncResult, object> endExecuteMethod,
      IAsyncResult asyncResult,
      IAsyncResult taskId,
      RetryAttempt taskRetryAttempt )
    {
      object queryResult = null;
      Exception exception = null;

      if( asyncResult.UseCallId( DataSourceProvider.ExecuteExceptionCallId ) )
      {
        exception = asyncResult.GetCallResult<Exception>();
      }
      else
      {
        try
        {
          queryResult = endExecuteMethod.Invoke( asyncResult );
        }
        catch( Exception e )
        {
          exception = e;
        }
      }

      // Any operation on the query result or any error handling must be
      // done on the thread that called BeginExecute in the first place
      // to avoid any racing condition.
      Action handleResult = () =>
      {
        bool retryTask = false;

        // Check for any error on the call to EndExecute.
        if( exception != null )
        {
          ErrorResultAction errorResult = this.HandleException( exception, taskRetryAttempt.Count );

          if( errorResult == ErrorResultAction.Retry )
          {
            taskRetryAttempt.Increment();
            retryTask = true;
          }
          else if( errorResult == ErrorResultAction.Abort )
          {
            // The exception will be thrown on the call to EndExecute*Query.
          }
          else
          {
            // A new type of result has been created but hasn't been handled.
            if( exception is DataSourceProviderException )
            {
              throw new DataSourceProviderException( "An unhandled exception was raised by the DataSourceProvider. Refer to the InnerException for more information.", exception );
            }
            else
            {
              throw exception;
            }
          }
        }

        bool isTaskCanceled = m_taskScheduler.IsCanceled( taskId );

        if( retryTask && !isTaskCanceled )
        {
          m_taskScheduler.Restart( taskId );
        }
        else
        {
          m_taskScheduler.Dequeue( taskId );

          if( !isTaskCanceled )
          {
            Tuple<object, Exception> result = new Tuple<object, Exception>(
                                                    queryResult,
                                                    exception );

            sendResultMethod.Invoke( result );
          }
        }
      };

      UIDispatcher.Instance.InvokeFromUIThread( handleResult );
    }

    #region Initialization Methods

    protected virtual void Initialize()
    {
    }

    internal void Init()
    {
      if( m_isInitialized )
        return;

      m_taskScheduler = new TaskScheduler( ( this as IAsyncDataSourceProvider ) );

      this.Initialize();

      m_isInitialized = true;
    }

    private bool m_isInitialized; //false

    #endregion

    #region GetElementMemberType Methods

    internal virtual bool FindElementMemberType( string memberName, out Type type )
    {
      type = null;

      Type elementType = this.ElementType;
      if( memberName == "." )
        return true;

      PropertyInfo propertyInfo = elementType.GetProperty( memberName );
      if( propertyInfo != null )
      {
        type = propertyInfo.PropertyType;
        return true;
      }

      FieldInfo fieldInfo = elementType.GetField( memberName );
      if( fieldInfo != null )
      {
        type = fieldInfo.FieldType;
        return true;
      }

      return false;
    }

    internal Type GetElementMemberType( string memberName )
    {
      if( memberName == null )
        throw new ArgumentNullException( "memberName" );

      // For Phone SDK Compatibility
      if( !StringHelper.IsNullOrWhiteSpace( memberName ) )
      {
        Type type;

        if( this.FindElementMemberType( memberName, out type ) )
          return type;
      }

      throw new ArgumentException( "The specified member name does not exist.", "memberName" );
    }

    internal bool TryGetElementMemberType( string memberName, out Type type )
    {
      type = null;

      // For Phone SDK Compatibility
      if( StringHelper.IsNullOrWhiteSpace( memberName ) )
        return false;

      return this.FindElementMemberType( memberName, out type );
    }

    #endregion

    #region Items Wrapping/Unwrapping Methods

    internal RawDataItem WrapDataObject( object item )
    {
      if( item == null )
        throw new ArgumentNullException( "item" );

      RawDataItem dataItem;
      if( !this.TryWrapDataObject( item, out dataItem ) )
        throw new DataInternalException();

      return dataItem;
    }

    internal virtual bool TryWrapDataObject( object item, out RawDataItem dataItem )
    {
      if( item != null )
      {
        // Retrieve the underlying user object.
        dataItem = item as RawDataItem;
        object userObject = ( dataItem != null )
                              ? dataItem.Data
                              : item;

        // Make sure the user object is of the appropriate type.
        if( this.IsOfElementType( userObject ) )
        {
          // Wrap the user object if it wasn't already wrapped.
          if( dataItem == null )
          {
            dataItem = new RawDataItem( item );
          }

          return true;
        }
      }

      dataItem = null;
      return false;
    }

    internal object UnwrapDataObject( RawDataItem dataItem )
    {
      if( dataItem == null )
        throw new ArgumentNullException( "dataItem" );

      object item;
      if( !this.TryUnwrapDataObject( dataItem, out item ) )
        throw new DataInternalException();

      return item;
    }

    internal virtual bool TryUnwrapDataObject( RawDataItem dataItem, out object item )
    {
      if( dataItem != null )
      {
        object userObject = dataItem.Data;

        if( this.IsOfElementType( userObject ) )
        {
          item = userObject;
          return true;
        }
      }

      item = null;
      return false;
    }

    internal object UnwrapLayer( RawDataItem dataItem )
    {
      if( dataItem == null )
        throw new ArgumentNullException( "dataItem" );

      object item;
      if( !this.TryUnwrapLayer( dataItem, out item ) )
        throw new DataInternalException( "The item cannot be retrieved." );

      return item;
    }

    internal virtual bool TryUnwrapLayer( RawDataItem dataItem, out object item )
    {
      return this.TryUnwrapDataObject( dataItem, out item );
    }

    private IEnumerable<RawDataItem> WrapDataObjects( IEnumerable items )
    {
      if( items == null )
        yield break;

      foreach( object item in items )
      {
        yield return this.WrapDataObject( item );
      }
    }

    private CountQueryResult WrapDataObjectQueryResult( CountQueryResult queryResult )
    {
      return queryResult;
    }

    private DataQueryResult WrapDataObjectQueryResult( DataQueryResult queryResult )
    {
      if( queryResult == null )
        return queryResult;

      IEnumerable<RawDataItem> dataItems = this.WrapDataObjects( queryResult.Data );

      return new DataQueryResult(
               dataItems,
               queryResult.InlineCount,
               queryResult.TotalCount );
    }

    private GroupCountQueryResult WrapDataObjectQueryResult( GroupCountQueryResult queryResult )
    {
      return queryResult;
    }

    private StatQueryResult WrapDataObjectQueryResult( StatQueryResult queryResult )
    {
      return queryResult;
    }

    private IEnumerable<ItemChangedDescription> WrapDataObjectItemsChangedDescription( IEnumerable<ItemChangedDescription> itemsChanged )
    {
      if( itemsChanged == null )
        yield break;

      foreach( ItemChangedDescription itemChanged in itemsChanged )
      {
        object item = itemChanged.Item;
        RawDataItem dataItem;

        if( this.TryWrapDataObject( item, out dataItem ) )
        {
          ItemChangedDescription newItemChanged = ( !object.Equals( item, dataItem ) )
                                                    ? itemChanged.CreateWithNewItem( dataItem )
                                                    : itemChanged;

          yield return newItemChanged;
        }
      }
    }

    private DataSourceChangedEventArgs WrapDataObjectEventArgs(
      DataSourceChangedEventArgs eventArgs )
    {
      DataSourceChangedAction action = eventArgs.Action;
      IEnumerable<RawDataItem> newItems = this.WrapDataObjects( eventArgs.NewItems );
      IEnumerable<RawDataItem> oldItems = this.WrapDataObjects( eventArgs.OldItems );
      IEnumerable<ItemChangedDescription> itemsChanged = this.WrapDataObjectItemsChangedDescription( eventArgs.ItemsChanged );

      DataSourceChangedEventArgs newEventArgs;
      if( action == DataSourceChangedAction.ItemChanged )
      {
        newEventArgs = new DataSourceChangedEventArgs( itemsChanged );
      }
      else
      {
        newEventArgs = new DataSourceChangedEventArgs( action, newItems.Cast<object>(), oldItems.Cast<object>() );
      }

      return newEventArgs;
    }

    private IEnumerable<object> UnwrapLayer( IEnumerable items )
    {
      if( items != null )
      {
        foreach( object item in items )
        {
          yield return this.UnwrapLayer( ( RawDataItem )item );
        }
      }
    }

    private CountQueryParameters UnwrapLayer(
      CountQueryParameters queryParameters )
    {
      return queryParameters;
    }

    private DataQueryParameters UnwrapLayer(
      DataQueryParameters queryParameters )
    {
      if( queryParameters.ItemJump == null )
        return queryParameters;

      RawDataItem itemJump = ( RawDataItem )queryParameters.ItemJump.Item;
      object newItemJump = this.UnwrapLayer( itemJump );
      if( object.Equals( newItemJump, itemJump ) )
        return queryParameters;

      ItemJumpRequest newItemJumpRequest = new ItemJumpRequest( newItemJump );
      return new DataQueryParameters(
               queryParameters.Take,
               queryParameters.Skip,
               queryParameters.Sort,
               queryParameters.Filter,
               newItemJumpRequest,
               queryParameters.Jump );
    }

    private GroupCountQueryParameters UnwrapLayer(
      GroupCountQueryParameters queryParameters )
    {
      return queryParameters;
    }

    private StatQueryParameters UnwrapLayer(
      StatQueryParameters queryParameters )
    {
      return queryParameters;
    }

    private IEnumerable<ItemChangedDescription> UnwrapLayer( IEnumerable<ItemChangedDescription> itemsChanged )
    {
      if( itemsChanged == null )
        yield break;

      foreach( ItemChangedDescription itemChanged in itemsChanged )
      {
        RawDataItem dataItem = ( RawDataItem )itemChanged.Item;
        object item = this.UnwrapLayer( dataItem );

        yield return ( !object.Equals( item, dataItem ) )
                       ? itemChanged.CreateWithNewItem( dataItem )
                       : itemChanged;
      }
    }

    private bool IsOfElementType( object item )
    {
      return ( item != null ) && ( this.ElementType.IsAssignableFrom( item.GetType() ) );
    }

    #endregion

    #region StatFunctions Engine Methods

    internal virtual void CheckIfStatFunctionIsInCalculation( StatFunction statFunction )
    {
      if( ( statFunction != null ) && ( statFunction.InCalculation ) )
        throw new DataInternalException();
    }

    internal virtual void SetStatFunctionCalculationState(
      StatFunction statFunction,
      bool inCalculation )
    {
      if( statFunction == null )
        return;

      if( inCalculation )
      {
        statFunction.StartCalculation();
      }
      else
      {
        statFunction.EndCalculation();
      }
    }

    private Func<AsyncCallback, object, IAsyncResult> CreateBeginExecuteStatQuery(
      Func<StatQueryParameters, AsyncCallback, object, IAsyncResult> method,
      StatQueryParameters queryParameters,
      StatFunction statFunction,
      IDictionary<StatFunction, StatResult> statResults )
    {
      Debug.Assert( ( method != null )
                 && ( statFunction != null ) );

      return
        ( cb, st ) =>
        {
          try
          {
            DataSourceProvider.PreparePrerequisiteFunctions( statFunction, statResults );

            this.SetStatFunctionCalculationState( statFunction, true );

            return method.Invoke( queryParameters, cb, st );
          }
          catch( Exception )
          {
            this.SetStatFunctionCalculationState( statFunction, false );
            throw;
          }
        };
    }

    private Func<IAsyncResult, StatQueryResult> CreateEndExecuteStatQuery(
      Func<IAsyncResult, StatQueryResult> method,
      StatFunction statFunction )
    {
      Debug.Assert( ( method != null )
                 && ( statFunction != null ) );

      return
        ( iar ) =>
        {
          try
          {
            return method.Invoke( iar );
          }
          catch( Exception e )
          {
            return new StatQueryResult( new StatResult( e ) );
          }
          finally
          {
            this.SetStatFunctionCalculationState( statFunction, false );
          }
        };
    }

    private IAsyncResult StartNativeStatQuery(
      StatQueryParameters queryParameters,
      StatFunction statFunction,
      IDictionary<StatFunction, StatResult> statResults,
      AsyncCallback callback,
      object state )
    {
      Debug.Assert( statFunction != null );

      // This anonymous method start the calculation of the StatFunction.
      Func<AsyncCallback, object, IAsyncResult> beginExecute =
        this.CreateBeginExecuteStatQuery(
          this.BeginExecuteStatQueryCore,
          queryParameters,
          statFunction,
          statResults );

      // This anonymous method retrieve the result of the StatFunction.
      Func<IAsyncResult, StatQueryResult> getResult =
        this.CreateEndExecuteStatQuery(
          this.EndExecuteStatQueryCore,
          statFunction );
      Func<IAsyncResult, object> endExecute = ( iar ) => getResult.Invoke( iar );

      return this.BeginExecute(
               DataSourceProvider.ExecuteStatQueryCallId,
               beginExecute,
               endExecute,
               callback,
               state );
    }

    private IAsyncResult StartPrerequisiteStatQuery(
      StatQueryParameters queryParameters,
      StatFunction statFunction,
      AsyncCallback callback,
      object state )
    {
      Debug.Assert( statFunction != null );

      AsyncResult asyncResult = new AsyncResult(
                                  DataSourceProvider.ExecuteStatQueryCallId,
                                  callback,
                                  state );

      // This anonymous method start the calculation of the StatFunction
      // from the values of the prerequisite functions.
      Func<AsyncCallback, object, IAsyncResult> beginExecute =
        this.CreateBeginExecuteStatQuery(
          this.BeginExecuteStatQueryPrerequisiteFunctions,
          queryParameters,
          statFunction,
          null );

      // This anonymous method retrieve the result of the StatFunction.
      Func<IAsyncResult, StatQueryResult> getResult =
        this.CreateEndExecuteStatQuery(
          this.EndExecuteStatQueryPrerequisiteFunctions,
          statFunction );
      Func<AsyncResult, IAsyncResult, DependentCallResult> endExecute =
        ( par, iar ) =>
        {
          StatQueryResult queryResult;
          Exception exception;

          try
          {
            queryResult = getResult.Invoke( iar );
            exception = null;
          }
          catch( Exception e )
          {
            queryResult = null;
            exception = e;
          }

          return new DependentCallResult(
                   new Tuple<object, Exception>( queryResult, exception ) );
        };

      asyncResult.Start( beginExecute, endExecute );

      return asyncResult;
    }

    private IAsyncResult BeginExecuteStatQueryPrerequisiteFunctions(
      StatQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      AsyncResult asyncResult = new AsyncResult(
                                  DataSourceProvider.ExecuteStatQueryPrerequisiteFunctionsCallId,
                                  callback,
                                  state );

      StatFunction statFunction = queryParameters.Function.StatFunction;
      List<StatFunction> dependentStatFunctions = new List<StatFunction>();

      // The value cannot be retrieved if one of the StatFunction cannot be evaluated.
      if( !this.GetQueryableStatFunctions( statFunction, dependentStatFunctions ) )
        throw new DataInternalException( "The StatFunction isn't supported by the DataSourceProvider." );

      Debug.Assert( dependentStatFunctions.Count > 0 );

      Dictionary<StatFunction, StatResult> statResults = new Dictionary<StatFunction, StatResult>(
                                                           new StatFunctionEqualityComparer() );
      Queue<Tuple<StatFunction, StatQueryParameters>> remainingQueries = new Queue<Tuple<StatFunction, StatQueryParameters>>();

      // Build the query parameters of each dependent StatFunction.
      foreach( StatFunction function in dependentStatFunctions )
      {
        StatQueryParameters newQueryParameters =
          new StatQueryParameters(
            new StatRequest( function ),
            queryParameters.Sort,
            queryParameters.Filter,
            queryParameters.FilterGroup );

        remainingQueries.Enqueue(
          new Tuple<StatFunction, StatQueryParameters>(
            function,
            newQueryParameters ) );
      }

      // This anonymous method starts a query to retrieve the value of one StatFunction.
      Func<AsyncCallback, object, IAsyncResult> beginGetStatValue =
        ( cb, st ) =>
        {
          Tuple<StatFunction, StatQueryParameters> query = remainingQueries.Peek();
          StatFunction currentStatFunction = query.Item1;

          if( currentStatFunction != statFunction )
            this.CheckIfStatFunctionIsInCalculation( currentStatFunction );

          this.InitializeStatFunction( currentStatFunction );

          return this.BeginExecuteNativeStatQuery(
                   query.Item2,
                   currentStatFunction,
                   statResults,
                   cb,
                   st );
        };

      // This anonymous method gets the value of a stat function and starts a query
      // for the next stat function that hasn't been evaluated yet.
      Func<AsyncResult, IAsyncResult, DependentCallResult> endGetStatValue = null;
      endGetStatValue =
        ( par, iar ) =>
        {
          StatQueryResult queryResult = this.EndExecuteNativeStatQuery( iar );
          StatResult statResult = queryResult.Result;

          if( statResult == null )
            return new DependentCallResult( queryResult );

          // Since the result was retrieve asynchronously, the previous
          // query results may not be up to date anymore.  We'll flag them
          // has estimated.
          if( !iar.CompletedSynchronously )
          {
            foreach( StatResult sr in statResults.Values )
            {
              sr.IsEstimate = true;
            }
          }

          StatFunction currentStatFunction = remainingQueries.Dequeue().Item1;
          statResults.Add( currentStatFunction, statResult );

          if( currentStatFunction == statFunction )
            return new DependentCallResult( queryResult );

          Debug.Assert( remainingQueries.Count > 0 );

          return new DependentCallResult( null, beginGetStatValue, endGetStatValue );
        };

      asyncResult.Start( beginGetStatValue, endGetStatValue );

      return asyncResult;
    }

    private StatQueryResult EndExecuteStatQueryPrerequisiteFunctions( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( DataSourceProvider.ExecuteStatQueryPrerequisiteFunctionsCallId );

      return asyncResult.GetCallResult<StatQueryResult>();
    }

    private IAsyncResult BeginExecuteNativeStatQuery(
      StatQueryParameters queryParameters,
      StatFunction statFunction,
      IDictionary<StatFunction, StatResult> statResults,
      AsyncCallback callback,
      object state )
    {
      Debug.Assert( ( statFunction != null ) && ( statResults != null ) );

      AsyncResult asyncResult = new AsyncResult(
                                  DataSourceProvider.ExecuteNativeStatQueryCallId,
                                  callback,
                                  state );

      StatResult statResult;

      // The StatFunction hasn't been calculated yet.
      if( !statResults.TryGetValue( statFunction, out statResult ) )
      {
        StatFunction[] prerequisiteFunctions = statFunction.PrerequisiteFunctions;
        bool canCalculateResult = ( !statFunction.RequiresAccumulation )
                               && ( prerequisiteFunctions != null )
                               && ( prerequisiteFunctions.Length > 0 )
                               && ( ( from pf in prerequisiteFunctions
                                      select statResults.ContainsKey( pf ) ).All( ( result ) => result ) );

        // The result can be calculated right away.
        if( canCalculateResult )
        {
          try
          {
            DataSourceProvider.PreparePrerequisiteFunctions( statFunction, statResults );

            statResult = statFunction.GetResult();
          }
          catch( Exception e )
          {
            statResult = new StatResult( e );
          }

          asyncResult.StartSync( new StatQueryResult( statResult ) );
        }
        else
        {
          asyncResult.Start(
            ( cb, st ) => this.StartNativeStatQuery( queryParameters, statFunction, statResults, cb, st ),
            ( par, iar ) => new DependentCallResult( this.EndExecuteStatQueryInternal( iar ) ) );
        }
      }
      // The StatFunction has already been calculated earlier.
      else
      {
        asyncResult.StartSync( new StatQueryResult( statResult ) );
      }

      return asyncResult;
    }

    private StatQueryResult EndExecuteNativeStatQuery( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( DataSourceProvider.ExecuteNativeStatQueryCallId );

      return asyncResult.GetCallResult<StatQueryResult>();
    }

    private void InitializeStatFunction( StatFunction statFunction )
    {
      if( statFunction == null )
        return;

      if( !statFunction.IsInitialized )
      {
        // Make sure the target StatFunction is valid.
        statFunction.Validate();

        IEnumerable<Type> dataTypes = ( from spn in statFunction.GetSourcePropertyNames()
                                        let mt = this.GetElementMemberType( spn )
                                        select ( mt.IsNullableStruct() )
                                                 ? Nullable.GetUnderlyingType( mt )
                                                 : mt );

        // Initialize the accumulators type.
        statFunction.InitializeAccumulationTypes( dataTypes.ToArray() );
      }
    }

    private static void PreparePrerequisiteFunctions(
      StatFunction statFunction,
      IDictionary<StatFunction, StatResult> statResults )
    {
      statFunction.Reset();

      if( statResults != null )
      {
        // Initialize the prerequisite if necessary.
        StatResult[] prerequisiteValues = DataSourceProvider.GetPrerequisiteValues(
                                            statFunction,
                                            statResults );

        if( ( prerequisiteValues != null ) && ( prerequisiteValues.Length > 0 ) )
        {
          for( int i = 0; i < prerequisiteValues.Length; i++ )
          {
            if( prerequisiteValues[ i ].Value is Exception )
              throw ( Exception )prerequisiteValues[ i ].Value;
          }

          statFunction.InitializePrerequisites( prerequisiteValues );
        }
      }
    }

    private bool GetQueryableStatFunctions(
      StatFunction targetStatFunction,
      IList<StatFunction> queryableStatFunctions )
    {
      if( targetStatFunction == null )
        return false;

      IEqualityComparer<StatFunction> comparer = new StatFunctionEqualityComparer();

      // The target StatFunction isn't planned already.
      if( !queryableStatFunctions.Contains( targetStatFunction, comparer ) )
      {
        // The target StatFunction is supported as it is.
        if( this.IsStatFunctionSupportedNatively( targetStatFunction ) )
        {
          queryableStatFunctions.Add( targetStatFunction );
        }
        // Check out if the StatFunction may be calculated from its dependent StatFunction.
        else
        {
          StatFunction[] prerequisiteFunctions = targetStatFunction.PrerequisiteFunctions;

          // The target StatFunction must use prerequisite functions.
          if( ( prerequisiteFunctions == null )
            || ( prerequisiteFunctions.Length <= 0 )
            || ( ( targetStatFunction.RequiresAccumulation )
              && ( !this.IsStatFunctionSupported( targetStatFunction ) ) ) )
            return false;

          foreach( StatFunction dependentStatFunction in prerequisiteFunctions )
          {
            if( !this.GetQueryableStatFunctions( dependentStatFunction, queryableStatFunctions ) )
              return false;
          }

          queryableStatFunctions.Add( targetStatFunction );
        }
      }

      return true;
    }

    private static StatResult[] GetPrerequisiteValues(
      StatFunction targetStatFunction,
      IDictionary<StatFunction, StatResult> statResults )
    {
      Debug.Assert( targetStatFunction != null );

      StatFunction[] prerequisiteFunctions = targetStatFunction.PrerequisiteFunctions;

      if( ( prerequisiteFunctions != null ) && ( prerequisiteFunctions.Length > 0 ) )
      {
        Debug.Assert( statResults != null );

        StatResult[] prerequisiteValues = new StatResult[ prerequisiteFunctions.Length ];

        for( int i = 0; i < prerequisiteFunctions.Length; i++ )
        {
          StatFunction prerequisiteFunction = prerequisiteFunctions[ i ];
          StatResult prerequisiteValue;

          if( !statResults.TryGetValue( prerequisiteFunction, out prerequisiteValue ) )
            throw new DataInternalException();

          prerequisiteValues[ i ] = prerequisiteValue;
        }

        return prerequisiteValues;
      }

      return null;
    }

    #endregion

    #region External Notification Methods

    public void NotifyReset()
    {
      this.InvalidateData();
    }

    public void NotifyItemsAdded( object itemAdded )
    {
      if( itemAdded == null )
        throw new ArgumentNullException( "itemAdded" );

      this.NotifyItemsAdded( new object[] { itemAdded } );
    }

    public void NotifyItemsAdded( IEnumerable itemsAdded )
    {
      if( itemsAdded == null )
        return;

      IEnumerable<object> items = itemsAdded.Cast<object>();
      if( !items.Any() )
        return;

      this.OnDataObjectsAdded( items );
    }

    public void NotifyItemsRemoved( object itemRemoved )
    {
      if( itemRemoved == null )
        throw new ArgumentNullException( "itemRemoved" );

      this.NotifyItemsRemoved( new object[] { itemRemoved } );
    }

    public void NotifyItemsRemoved( IEnumerable itemsRemoved )
    {
      if( itemsRemoved == null )
        return;

      // Retrieve the items that are about to be removed.
      List<RawDataItem> dataItemsRemoved = new List<RawDataItem>();
      foreach( object item in itemsRemoved )
      {
        RawDataItem dataItem;
        if( this.TryWrapDataObject( item, out dataItem ) )
        {
          dataItemsRemoved.Add( dataItem );
        }
      }

      IEnumerable<object> unwrappedItemsRemoved = this.UnwrapLayer( dataItemsRemoved );
      if( unwrappedItemsRemoved.Any() )
      {
        this.OnDataObjectsRemoved( unwrappedItemsRemoved );
      }
    }

    public void NotifyItemsChanged( ItemChangedDescription itemChanged )
    {
      if( itemChanged != null )
      {
        this.NotifyItemsChanged( new ItemChangedDescription[] { itemChanged } );
      }
    }

    public void NotifyItemsChanged( IEnumerable<ItemChangedDescription> itemsChanged )
    {
      if( ( itemsChanged == null ) || ( !itemsChanged.Any() ) )
        return;

      IEnumerable<ItemChangedDescription> wrappedItemsChanged = this.WrapDataObjectItemsChangedDescription( itemsChanged );

      this.InvalidateDataItem( wrappedItemsChanged );
    }

    #endregion

    #region Internal Notification Methods

    protected virtual void OnInvalidateData()
    {
      this.OnDataSourceChanged( new DataSourceChangedEventArgs( DataSourceChangedAction.Reset ) );
    }

    protected virtual void OnInvalidateDataObjects( IEnumerable<ItemChangedDescription> itemsChanged )
    {
      this.OnDataSourceChanged( new DataSourceChangedEventArgs( itemsChanged ) );
    }

    protected virtual void OnDataObjectsAdded( IEnumerable<object> itemsAdded )
    {
      List<object> dataItemsAdded = new List<object>();
      foreach( object item in itemsAdded )
      {
        RawDataItem dataItem;
        if( this.TryWrapDataObject( item, out dataItem ) )
        {
          dataItemsAdded.Add( dataItem );
        }
      }

      if( dataItemsAdded.Count > 0 )
      {
        DataSourceChangedEventArgs eventArgs = new DataSourceChangedEventArgs(
                                                 DataSourceChangedAction.Add,
                                                 dataItemsAdded,
                                                 null );
        this.OnDataSourceChanged( eventArgs );
      }
    }

    protected virtual void OnDataObjectsRemoved( IEnumerable<object> itemsRemoved )
    {
      if( itemsRemoved.Any() )
      {
        DataSourceChangedEventArgs eventArgs = new DataSourceChangedEventArgs(
                                                 DataSourceChangedAction.Remove,
                                                 null,
                                                 itemsRemoved );
        this.OnDataSourceChanged( eventArgs );
      }
    }

    internal virtual void InvalidateData()
    {
      this.OnInvalidateData();
    }

    internal void InvalidateDataItem( RawDataItem item )
    {
      ItemChangedDescription itemChanged = new ItemChangedDescription( item );
      this.InvalidateDataItem( itemChanged );
    }

    internal void InvalidateDataItem( ItemChangedDescription itemChanged )
    {
      if( itemChanged == null )
        throw new ArgumentNullException( "itemChanged" );

      this.InvalidateDataItem( new ItemChangedDescription[] { itemChanged } );
    }

    internal virtual void InvalidateDataItem( IEnumerable<ItemChangedDescription> itemsChanged )
    {
      if( itemsChanged == null )
        throw new ArgumentNullException( "itemsChanged" );

      // There is no need to go further if no item has changed.
      List<ItemChangedDescription> itemsChangedList = itemsChanged.ToList();
      if( itemsChangedList.Count <= 0 )
        return;

      Type elementType = this.ElementType;
      foreach( ItemChangedDescription itemChanged in itemsChangedList )
      {
        RawDataItem dataItem = itemChanged.Item as RawDataItem;
        if( dataItem == null )
          throw new DataInternalException();

        // Make sure the data object is of the user object type.
        if( !this.IsOfElementType( dataItem.Data ) )
          throw new DataInternalException();
      }

      IEnumerable<ItemChangedDescription> unwrappedItemsChanged = this.UnwrapLayer( itemsChangedList );

      this.OnInvalidateDataObjects( unwrappedItemsChanged );
    }

    #endregion

    #region Private Fields

    private TaskScheduler m_taskScheduler; //null

    #endregion

    #region Private RetryAttempt Nested Type

    private class RetryAttempt
    {
      internal RetryAttempt()
      {
        this.Count = 0;
      }

      internal int Count
      {
        get;
        private set;
      }

      internal void Increment()
      {
        if( this.Count == Int32.MaxValue )
          return;

        this.Count++;
      }
    }

    #endregion

    #region Private ScheduleTask Nested Type

    private class ScheduleTask
    {
      internal ScheduleTask( IAsyncResult taskId, Action task )
      {
        if( taskId == null )
          throw new ArgumentNullException( "taskId" );

        if( task == null )
          throw new ArgumentNullException( "task" );

        this.Id = taskId;
        this.Task = task;
        this.IsCanceled = false;
      }

      internal IAsyncResult Id
      {
        get;
        private set;
      }

      internal Action Task
      {
        get;
        private set;
      }

      internal bool IsCanceled
      {
        get;
        private set;
      }


      internal void Cancel()
      {
        this.IsCanceled = true;
      }
    }

    #endregion

    #region Private TaskScheduler Nested Type

    private class TaskScheduler
    {
      internal TaskScheduler( IAsyncDataSourceProvider throttler )
      {
        m_throttler = throttler;
      }

      private ScheduleTask CurrentTask
      {
        get
        {
          return m_queue.First.Value;
        }
      }

      private static bool IsSameTask( ScheduleTask scheduledTask, IAsyncResult taskId )
      {
        return ( scheduledTask != null && object.ReferenceEquals( scheduledTask.Id, taskId ) );
      }

      private bool IsCurrentTask( IAsyncResult taskId )
      {
        if( m_queue.Count <= 0 )
          return false;

        return TaskScheduler.IsSameTask( this.CurrentTask, taskId );
      }

      private ScheduleTask Find( IAsyncResult taskId )
      {
        if( m_queue.Count <= 0 )
          return null;

        foreach( ScheduleTask scheduledTask in m_queue )
        {
          if( TaskScheduler.IsSameTask( scheduledTask, taskId ) )
            return scheduledTask;
        }

        return null;
      }

      private bool Contains( IAsyncResult taskId )
      {
        return ( this.Find( taskId ) != null );
      }

      private bool RemoveTask( IAsyncResult taskId )
      {
        if( m_queue.Count <= 0 )
          return false;

        bool removed = false;
        LinkedListNode<ScheduleTask> node = m_queue.First;

        while( node != null )
        {
          if( TaskScheduler.IsSameTask( node.Value, taskId ) )
          {
            LinkedListNode<ScheduleTask> nextNode = node.Next;

            m_queue.Remove( node );
            removed = true;

            node = nextNode;
          }
          else
          {
            node = node.Next;
          }
        }

        return removed;
      }

      private void InvokeTask()
      {
        Action startTask = () =>
        {
          lock( m_lock )
          {
            while( m_queue.Count > 0 )
            {
              ScheduleTask currentTask = this.CurrentTask;

              if( currentTask.IsCanceled )
              {
                m_queue.RemoveFirst();
              }
              else
              {
                m_lastTickCount = Environment.TickCount;
                currentTask.Task.Invoke();

                break;
              }
            }
          }
        };

        int waitTickCount = 0;

        if( m_throttler != null && m_lastTickCount.HasValue )
        {
          int waitTime = Convert.ToInt32( m_throttler.QueryRequestInterval.TotalMilliseconds );
          int elapseTime = Environment.TickCount - m_lastTickCount.Value;

          waitTickCount = waitTime - elapseTime;
        }

        if( waitTickCount <= 0 )
        {
          startTask.Invoke();
        }
        else
        {
          DispatcherTimer timer = new DispatcherTimer();

          timer.Interval = new TimeSpan( 0, 0, 0, 0, waitTickCount );
          timer.Tick += ( object sender, EventArgs e ) =>
          {
            DispatcherTimer oneTime = ( DispatcherTimer )sender;
            oneTime.Stop();

            startTask.Invoke();
          };

          timer.Start();
        }
      }

      internal bool Enqueue( IAsyncResult taskId, Action task )
      {
        if( taskId == null )
          throw new ArgumentNullException( "taskId" );

        if( task == null )
          throw new ArgumentNullException( "task" );

        lock( m_lock )
        {
          if( this.Contains( taskId ) )
            return false;

          ScheduleTask scheduledTask = new ScheduleTask( taskId, task );

          m_queue.AddLast( scheduledTask );

          // Start the task right away if it is the only task in the queue.
          if( m_queue.Count == 1 )
          {
            this.InvokeTask();
          }

          return true;
        }
      }

      internal bool Restart( IAsyncResult taskId )
      {
        lock( m_lock )
        {
          if( taskId == null || m_queue.Count <= 0 )
            return false;

          if( !this.IsCurrentTask( taskId ) )
            throw new InvalidOperationException( "An attempt was made to restart a task that has not been started yet." );

          ScheduleTask currentTask = this.CurrentTask;

          this.RemoveTask( taskId );
          m_queue.AddFirst( currentTask );
          this.InvokeTask();

          return true;
        }
      }

      internal bool Dequeue( IAsyncResult taskId )
      {
        lock( m_lock )
        {
          if( taskId == null || m_queue.Count <= 0 )
            return false;

          bool isFirst = TaskScheduler.IsSameTask( this.CurrentTask, taskId );
          bool removed = this.RemoveTask( taskId );

          if( isFirst && m_queue.Count > 0 )
          {
            this.InvokeTask();
          }

          return removed;
        }
      }

      internal bool IsCanceled( IAsyncResult taskId )
      {
        if( taskId == null )
          return true;

        lock( m_lock )
        {
          ScheduleTask scheduledTask = this.Find( taskId );

          return ( scheduledTask != null )
                   ? scheduledTask.IsCanceled
                   : true;
        }
      }

      internal bool Cancel( IAsyncResult taskId )
      {
        if( taskId == null )
          return false;

        lock( m_lock )
        {
          ScheduleTask scheduledTask = this.Find( taskId );

          if( scheduledTask != null )
          {
            scheduledTask.Cancel();

            return true;
          }

          return false;
        }
      }

      private LinkedList<ScheduleTask> m_queue = new LinkedList<ScheduleTask>();
      private IAsyncDataSourceProvider m_throttler; //null
      private Nullable<int> m_lastTickCount; //null
      private object m_lock = new object();
    }

    #endregion

    #region Private StatFunctionEqualityComparer Nested Type

    private class StatFunctionEqualityComparer : IEqualityComparer<StatFunction>
    {
      public int GetHashCode( StatFunction obj )
      {
        if( obj == null )
          return 0;

        return obj.GetEquivalenceKey();
      }

      public bool Equals( StatFunction x, StatFunction y )
      {
        return StatFunction.AreEquivalents( x, y );
      }
    }

    #endregion

    #region Private CanFilterByVisitor Nested Type

    private class CanFilterByVisitor : FilterExpressionVisitor
    {
      internal CanFilterByVisitor(
        FilterExpressionBase expression,
        Func<string, Type> getElementMemberType,
        Func<Type, IEnumerable<FilterOperator>> getSupportedFilterOperators )
        : base( expression )
      {
        if( getElementMemberType == null )
          throw new ArgumentNullException( "getElementMemberType" );

        if( getSupportedFilterOperators == null )
          throw new ArgumentNullException( "getSupportedFilterOperators" );

        m_getElementMemberType = getElementMemberType;
        m_getSupportedFilterOperators = getSupportedFilterOperators;
      }

      internal bool Result
      {
        get
        {
          if( !m_result.HasValue )
          {
            this.Visit();
          }

          return m_result.GetValueOrDefault( false );
        }
      }

      protected override void Initialize()
      {
        m_result = null;

        base.Initialize();
      }

      protected internal override void Visit( FilterExpression expression )
      {
        if( m_result.GetValueOrDefault( true ) )
        {
          try
          {
            Type propertyType = m_getElementMemberType.Invoke( expression.MemberName );

            m_result = m_getSupportedFilterOperators.Invoke( propertyType ).Contains( expression.FilterOperator );
          }
          catch( Exception )
          {
            m_result = false;
          }
        }
      }

      protected internal override void Visit( AndFilterExpression expression )
      {
        this.Visit( ( FilterExpressionOperator )expression );
      }

      protected internal override void Visit( OrFilterExpression expression )
      {
        this.Visit( ( FilterExpressionOperator )expression );
      }

      protected internal override void Visit( NotFilterExpression expression )
      {
        if( m_result.GetValueOrDefault( true ) )
        {
          this.Visit( expression.ChildExpression );
        }
      }

      private void Visit( FilterExpressionOperator expression )
      {
        if( m_result.GetValueOrDefault( true ) )
        {
          foreach( FilterExpressionBase childExpression in expression.ChildExpressions )
          {
            // At least one child expression isn't supported.
            if( m_result == false )
              return;

            this.Visit( childExpression );
          }
        }
      }

      private Nullable<bool> m_result; //null
      private Func<string, Type> m_getElementMemberType;
      private Func<Type, IEnumerable<FilterOperator>> m_getSupportedFilterOperators;
    }

    #endregion
  }
}
